package gui;

import game.Game;

import javax.annotation.PreDestroy;
import javax.swing.*;

import java.awt.*;
import java.awt.image.*;

/**
 * @author Ryan Hill
 */
public class GUI extends JFrame implements Runnable
{
	/**
	 * The serial version uid for the application
	 */
	private static final long serialVersionUID = 3648112661186903330L;

	/**
	 * Average frame rate of the GUI
	 */
	private final int rate = 15; 
	
	/**
	 * Actual frame rate
	 */
	private double frameRate;
	
	/**
	 * Current game time; used in calculation for duration of one frame
	 */
	private long time;
	
	/**
	 * The game that is running in the window
	 */
	private Game game;
	
	private Color bgColor;
	
	private boolean displayFPS;
	
	/**
	 * static void main( String[] args )
	 * 
	 * Main method that instantiates a new Thread with GUI and 
	 * calls its start method
	 * 
	 * @param args
	 */
	public static void main( String[] args )
	{
		( new Thread( m_rFrame = new GUI() ) ).start();
	}
	
	/**
	 * Singleton data for the GUI
	 */
	private static GUI m_rFrame;
	/**
	 * Graphics Device for the Full Screen Exclusive Mode
	 */
	private GraphicsDevice m_rGD;
	
	/**
	 * GUI()
	 * 
	 * Constructor for the GUI class
	 * Since GUI is a singleton class the constructor is
	 * a private method to limit its use too one class
	 */
	private GUI()
	{
		 initScreen();
		 
		 time = System.currentTimeMillis();
		 game = new Game();
		 
		 frameRate = 0;
		 
		 bgColor = Color.black;
		 
		 displayFPS = true;
	
		 this.setDefaultCloseOperation( WindowConstants.DISPOSE_ON_CLOSE );
		 this.addKeyListener( new GUIKeyListener( game.getUser() ) );
		 this.addMouseMotionListener( new GUIMouseMotionListener( game.getUser() ) );
		 this.addMouseListener( new GUIMouseListener( game.getUser() ) );
		 this.addMouseWheelListener( new GUIMouseWheelListener() );
	}
	
	/**
	 * void restoreScreen()
	 * 
	 * As the program exits, call restore screen to dispose
	 * the GUI's graphics
	 */
	@PreDestroy
	public void restoreScreen()
	{
		JFrame f = (JFrame)m_rGD.getFullScreenWindow();
		
		try 
		{
			if( f != null )
				f.dispose();
		}
		catch( IllegalArgumentException err ){}
		
		m_rGD.setFullScreenWindow( null );
	}
	
	/**
	 * void initScreen()
	 * 
	 * Initializes the GUI's GraphicsEnvironment, sets it to fullscreen mode
	 * and creates the double buffer
	 */
	private void initScreen()
	{
		GraphicsEnvironment graphicsEnvironment = GraphicsEnvironment.getLocalGraphicsEnvironment();
		m_rGD = graphicsEnvironment.getDefaultScreenDevice();
		
		this.setUndecorated( true );
		this.setIgnoreRepaint( true );
		try {
			if ( m_rGD.isFullScreenSupported() ) 
			{
				m_rGD.setFullScreenWindow( this );
				m_rGD.setDisplayMode( new DisplayMode( 1024, 768, 32, DisplayMode.REFRESH_RATE_UNKNOWN ) );
		    }
			 
			this.createBufferStrategy( 2 );
		} 
		catch( Exception err )
		{
			m_rGD.setDisplayMode( m_rGD.getDisplayMode() );
			m_rGD.setFullScreenWindow( null );
		}	 
	}

	@Override
	public void run()
	{
		while( true )
		{
			double duration = ( System.currentTimeMillis() - time ) / 1000.0;
			game.update( duration );
			
			frameRate = 1 / duration;
			
			render();
			
			time = System.currentTimeMillis();
			
			try
			{
				Thread.sleep( rate );
			}
			catch( InterruptedException err )
			{
				return;
			}
		}
	}
	
	/**
	 * Renders the graphics on the screen
	 */
	private void render()
	{
		try
		{
			BufferStrategy bufferStrategy = this.getBufferStrategy();
			
			Graphics2D g2d = (Graphics2D)bufferStrategy.getDrawGraphics();
			
			g2d.setRenderingHint( RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON );
			
			g2d.setColor( bgColor );
			g2d.fillRect( 0, 0, this.getWidth(), this.getHeight() );
			
			game.render( g2d );
			
			if( displayFPS )
			{
				g2d.setColor( Color.white );
				g2d.drawString( "Expected FPS: " + Math.round( getExpectedRate() ) + " FPS: " + Math.round( frameRate ), 50, 50 );
			}
			
			g2d.dispose();

			if( !bufferStrategy.contentsLost() )
			{
				bufferStrategy.show();
			}
		}
		catch ( IllegalStateException err ) {}
	}
	
	public void setBGColor( Color c )
	{
		bgColor = c;
	}
	
	public void setDisplayFPS( boolean b )
	{
		displayFPS = b;
	}
	
	/**
	 * Sets the cursor image for the GUI
	 * 
	 * @param img image that the cursor is going to take on
	 */
	public void setCursor( Image img )
	{
		Toolkit t = Toolkit.getDefaultToolkit();
		
		GUI.getInstance().setCursor( t.createCustomCursor( img, new Point( 0, 0 ), "cursor" ) );
	}
	
	/**
	 * @return the expected fps for the application
	 */
	public double getExpectedRate()
	{
		return 1000.0/rate;
	}
	
	/**
	 * @return the actual fps for the application
	 */
	public double getActualRate()
	{
		return frameRate;
	}

	/**
	 * @return the singleton for GUI
	 */
	public static GUI getInstance()
	{
		return m_rFrame;
	}
}
